Phase 7.5 — Audio and Video in Pure PHP

Markers: being worked on, implemented and waiting to be reviewed, done, closed by deciding not to do it, ? open decision, unmarked = planned.

Arc Post-Mortem Summary

This arc gave Yioop its own audio and video, then put the code that had grown around them in order. Yioop reads Vorbis, Opus, FLAC and the container formats in PHP alone, records a message in the browser and plays it back without ffmpeg, and a page's resource folder recodes what is put in it (I, II). The library was grouped into folders by what its classes are for (III), and the four files over five thousand lines were split: WikiElement gave up its git pages, GroupModel became a base with a wiki model and a feed model under it, and SocialComponent fell from 16,932 lines to 5,215 as the base that WikiComponent, FeedComponent, ResourceComponent, MessageComponent, MailComponent and GitComponent extend (IV).

Two rules came out of that splitting and are now in the process file. A component never calls another component: what two of them need sits in the base they extend, is protected rather than private, and takes its constants with it. And the home of every method is settled before anything moves, because moving whatever the last measurement named makes new crossings on each pass. The faults this arc left for Chris to find were all of one shape, a name that moved out from under a caller, and each is now a reading a patch cannot pass: every call on a model, every call on a component, every name a test asks of one.

The arc closed on the screens themselves. The wiki editor's preview stamps each line of the source it drew, so the line the caret sits on can be put in the middle of the preview rather than the two panes being lined up by a ratio of their heights. A wiki link may name the language of the page it wants, and a resource marker the folder its file sits in, both read as markup rather than as part of a name.

What this arc is for

Yioop reaches for ffmpeg to make a thumbnail from a video, to work out how long one runs, and to make over a recorded message. That means a site cannot do any of it without a program installed beside it, and a site that has one runs somebody else's program on files that arrived from the web. Two pure-PHP libraries written elsewhere do both jobs, and this arc brings them in, wires them where ffmpeg is reached for today, and gives them one command-line front end (I). It then settles what a page's resource folder should do about recoding what is dropped into it (II). With the library folder about to gain a good many files, it groups that folder into subfolders (III), and closes by looking for the places the code has grown past what a reader can hold (IV).

  1. Bring the pure-PHP audio and video libraries into Yioop. Both were written in another conversation. They go under src/library/av_processing, in a namespace of that name. Each gets one command line front end and Yioop's own unit tests.
    1. Split VideoThumb.php into twenty-three files the autoloader can find. The file held fifty-eight classes in nineteen thousand lines. They run from bit readers to whole decoders for five video formats. Every class another file reaches for now has a file named after it, so nothing requires anything. Names are built from English words as Coding.pdf asks, and a docblock says what its subject is for. Checked by decoding. A ten second MP4 and a five second WebM each give a thumbnail at the width asked for.
    2. Move its command line into AVTool.php. AVTool.php holds the help text and the option handler. It makes a picture from a video, and turns a recording into an MP4 every browser plays. It refuses to run from a web request. Which half runs is settled by the file's first bytes, not its name. Twelve cases run it and the audio class as a person would.
    3. Put the audio library under the same folder and namespace. Thirty-one files came in. They are readers for Ogg and WebM, a decoder for Opus, an encoder, and a writer for the sound file. They sit beside the video work under the arc's namespace, and the loader they shipped with is dropped. AudioConverter turns a recording into an MP4 a few seconds at a time, and PlainSound reads WAV and AIFF. Any kind the folder reads converts to any kind it writes. Reading Vorbis, MP3 and AAC is not written yet, and those are refused with a sentence saying so.
    4. Rewrite its tests as Yioop unit tests. Sixteen test files came in, with five small recordings and three helper classes. The runner and the test class the library shipped with are dropped, since Yioop has both. Each test names the classes by the arc's namespace and finds its recordings where Yioop keeps them. They run as they stand: 131 cases pass under CodeTool.php unit. Two run for about a hundredth of a second, which is real decoding rather than a fault. Each says so in its own docblock.
    5. A video's thumbnail and its running time come from the library. Two places asked ffmpeg for a picture and ffprobe for a running time. Both read the file with VideoExtractor now. A thumbnail is made where no ffmpeg is set at all. Measured on a five second WebM: 5.01 seconds, 320 by 240, a still picture in a tenth of a second. Both thumbnails are WebP now, the moving one named .animated.webp. A served file's type is read from its name rather than its bytes.
    6. Say where ffmpeg is still reached for. Seven files name it, counting the setting FFMPEG and the plain word separately. Two are replaced by this arc, two should be replaced later, and three stay. The audit below says which is which and why.
      1. Replaced by this arc. GroupModel's convertRecordingToMp4 runs ffmpeg to make a recording over into mp4; that is item H. UsermessagesElement offers the record button only where FFMPEG is set, and stops gating on it once H lands.
      2. Should be replaced later. VideoConvertJob cuts an uploaded video into short segments with ffmpeg and stitches them back. It also recodes avi or mov to mp4. Its thumbnail step is replaced already. PodcastDownloadJob stitches podcast segments and now names ffmpeg by the setting. Both are background jobs run by the media updater.
      3. Stays. Config.php declares the setting itself. PublicHelpPages tells a reader how to install ffmpeg. GroupModel schedules an uploaded video for recoding behind the setting. It makes a voice message over with the audio library, which is item H.
      4. Gates removed. Three places asked whether FFMPEG was set before work that no longer needs it. They are a video page's size and running time, a video's thumbnail, and VideoConvertJob's thumbnail step. All three read the file with the library now.
    7. Write unit tests for the video side. The video library arrives with none. Each container reader and each decoder gets cases that read a small file of that kind. They check the frame that comes back, so one decoder cannot break another unseen.
    8. A recorded message is made over without ffmpeg. Saving a recording turns whatever arrived into one kind, by handing the file to ffmpeg. That path uses the audio library instead. A site with no ffmpeg can then still take a voice message. A browser records the way built for speech, which Yioop could not decode, so that decoder is the work below.
      1. Read the opening of a stretch of speech. The marks at the front of a packet now read, one for each stretch. The kind of sound and the loudness of each quarter read as well. The tables come from the counts the standard fixes. Each is walked the way the range decoder walks a table.
      2. Read the shape a stretch was spoken with. A stretch names a guess from a fixed book and mends each of its sixteen frequencies. Those frequencies now read, and are pushed apart where two sit too close. Every shape of the ten second sample comes back rising in order, which any mistake in that arithmetic would break.
      3. Read the pitch of a voiced stretch. How far back the sound repeats now reads, along with a filter of five taps for each quarter. The distance is carried from one stretch to the next, since a stretch may write it as a move. Half the jumps between neighboring stretches are three samples. That is a voice moving four cycles a second.
      4. Read the pulses of a stretch. The counts, the splitting and the signs all read now. Against the reference decoder, built here and made to print what it reads, the two agree sample for sample. That is 198 packets of speech and 63,360 samples. Two faults were found this way. A block is split one half all the way down before the other, and the sign sets run seven apart.
      5. Turn shape, pitch and pulses back into samples. The scale each quarter is played at now comes out of the loudness numbers. All four scales of all 198 packets match the reference decoder. The frequencies, the filter terms and the rough sound all match on every packet. Every packet agrees with the reference decoder, sample for sample.
        1. A stretch says how much of the earlier sound to lean on. A stretch standing on its own writes one of three answers before its pulses. That answer now reads, which is the first piece of carrying sound between stretches.
        2. A voiced stretch reaches into the sound before it. The samples already played are run backward through the shape filter. They are then put at the pitch filter's scale. The five taps of a pitch filter are held with seven bits of fraction. The filter works with fourteen, so each is shifted up. The rough sound of a voiced quarter sits within a percent of the reference. It had no relation at all before.
        3. The whole chain runs from a file to samples. SpeechDecoder walks a recording and keeps what one stretch leaves the next. Ten seconds decodes in a fifth of a second. AVTool writes the result as a wave file with --speech. Against the reference decoder it agrees sample for sample: 198 packets and 63,360 samples.
      6. Carry a stretch's state into the next one. SpeechDecoder keeps the filters, the loudness and the samples already played across a packet boundary. A packet may carry a spare copy of a stretch for a listener who lost the one before. That copy is read past so the stretch behind it is found, then thrown away.
      7. Give the samples to the converter. AudioConverter sends a speech recording to the new decoder rather than refusing it. The samples are raised to forty-eight thousand a second, the rate Opus plays at, by weighing four samples either side. A browser writes its recording in parts, and its pieces are found from the heading of each part. GroupModel makes a voice message over with this converter, so a site without ffmpeg can take one.
  2. Settle what a page's resource folder does about recoding. A file dropped into a wiki page's folder is stored as it arrived, apart from recordings, which are made over. What should happen for video, and for audio that is already in the kind Yioop wants, is not settled.
    1. Decide what copyFileToGroupPageResource should recode. A file carrying pictures is stored as it arrived. Sound alone is made over into the one kind every browser plays. The file itself is read rather than its name or the kind it claims, since a browser recording a voice names itself a video kind.
    2. Read VideoConvertJob and PodcastDownloadJob against that decision. VideoConvertJob is deleted. Its only work came from marker files GroupModel wrote for an uploaded video. That block is gone, so a video is stored like any other file. PodcastDownloadJob keeps its own use of the convert folder and still names ffmpeg by the setting.
  3. Group src/library into subfolders. The folder holds files of every kind side by side, and this arc adds a hundred more. Related files are gathered into subfolders so a reader can find their way, with the namespaces and every reference following.
    1. Say what the groups are. A pass over the folder naming which files belong together, so the moving is one decision rather than a hundred.
    2. A refused add user form comes back holding what was typed. Manage Users sent the writer to an empty form when the two passwords differed. The five boxes that are not passwords travel with the refusal, so the form opens again holding them. A password is not among them, since it does not belong in an address. The browser compares the two password boxes before the form is sent, so a mismatch costs no trip to the server. Where the server does the refusing, the add user row stays open.
    3. Expanding a group's members leaves the reader looking at them. The members count on a group's settings page reloads the page with the list shown. The reload left the reader at the foot of the page rather than at the list. The Membership fieldset now carries an id and the link ends with it. The browser lands on that section with its legend in view.
    4. The server says how much memory it holds against the requests it has served. A figure that climbs on its own does not say whether the climbing comes from work done or from time passing. Every five minutes a line now reports the growth, the requests served between two lines, and the bytes each. A steady figure per request says something is held between requests. A falling one says memory is claimed once and reused.
    5. A git object too large to rebuild is refused rather than fatal. Rebuilding an object from a delta holds the old and new versions at once and grows the new one by doubling. That comes to about two and a half times the object, measured at 218 megabytes for a 90 megabyte rebuild. The ceiling of 512 megabytes therefore allowed an object the server could not carry, and seekquarry.com died with a memory fatal. It is now 192 megabytes.
    6. Move the files and follow the references. Fifty files moved into six folders. Each gained its group's namespace. A moved class is named six ways and each was followed. Those are a name written in full, the L alias, and a name built out of NS_LIB. Also a callback, a __DIR__ load, and a loose library function. A file naming a group takes an alias for it, such as LW for the wiki group. The root index.php is followed too, since it sits outside src.
  4. Look for the places the code has grown past reading. Yioop has methods and files long enough that a reader cannot hold them, and passages repeated in several places. This section finds them and says which are worth taking apart.
    1. Find the methods over five hundred lines. A list of them and what each does. For each, whether it splits along a line a reader would know.
      1. The edit page form of the wiki view is split, 1,093 lines to 493. renderEditPageForm, split by fieldset.
      2. The group feed activity is split, 1,070 lines to 130. groupFeeds, one method an arm of its switch: toggleThreadMail, addComment, addFeedContact, addFeedGroup, copyOrCutPost, deletePost, deleteClip, emptyClip, pasteAllPosts, pastePost, flagPost, approveFlagged, deleteFlagged, downVotePost, startNewThread, feedStatus, updatePost, upVotePost.
      3. The wiki activity is split, 930 lines to 189. wiki, into cleanWikiRequestFields, deleteWikiPage, wikiHistory, wikiPageList, wikiRelationships, wikiSource, addWikiHistoryPage, addWikiReadPage, initializeWikiEditAndSource.
      4. The wiki editor activity is split, 906 lines to 198. editWiki, into editWikiResourceFile, editWikiHeadVars, editWikiPageSettings, editWikiResourceAction, editWikiUploadResource.
      5. The manage groups activity is split, 820 lines to 323. manageGroups, one method an arm of its switch: activateOrPromoteUsers, addUserToGroup, banGroupUser, createGroup, deleteGroup, deleteGroupUser, groupSettings, infoGroup, changeMailPreference, inviteGroupUsers, joinGroup, groupGraphStats, setMemberAccess, setPostLifetime, setVoteAccess, setRegisterType, groupStatistics, unsubscribeFromGroup.
      6. The search sources activity is split, 646 lines to 263. searchSources, one method an arm of its switch: addMediaSource, addSubsearch, clearSourceData, deleteMediaSource, deleteSubsearch, editSubsearch, editMediaSource, searchMediaSources, searchSubsearches, testMediaSource.
      7. The page options activity is split, 627 lines to 277. pageOptions, into pageOptionsCrawlSettings and pageOptionsTestPage. This one has no switch: it is a run of blocks, and the two taken out are the settling of what a crawl will run with and the trying of one page through the processors.
      8. The manage users activity is split, 565 lines to 172. manageUsers, one method an arm of its switch: addUser, editUser, actAsUser, deleteUser, addUserRole, addUserGroup, deleteUserRole, deleteUserGroup, searchUsers, updateUserStatus.
    2. Put back the methods that were never worth making, and take out the passages that are. A method under five lines called fewer than three times, and a method under ten lines called fewer than twice, buys a reader nothing: its body goes back where it was called from. A passage over five lines standing in three places or more is the opposite case, and wants a method of its own. Each with what it does and where it should live.
      1. Forty-one methods nothing can reach are taken away. Together they held 1,043 lines. devlog/check_orphans.sh names a method only where its name appears nowhere else in the source at all, since a name is reached in ways one line does not show: IndexShard's makeWords is written inside a longer string and called through it, and MailSite declares userExists and getPasswordHash for the class under library/mail to answer for. Both were taken away by a looser rule first and stopped the test run.
      2. The classifier tool's seven methods are put back. They were taken away as unreachable, but its main builds the name of what to run out of what the command line said, so run{$activity} reaches runTrainAndTest and that reaches the other six. The check now reads the fronts and backs joined into a name that is called through a variable, so ArcTool's outputInfo and this run are both known. An advertisement comparator is put back too, and the check leaves what handles advertisements and credits alone, since a script people buy calls into it from outside this source.
      3. Methods nothing can reach are taken away. Sixty-two of them, near five thousand lines, from a tokenizer that ran another language’s interpreter to the deprecated index shard’s slice readers. A method only a test called went with its case, since such a case stands for a caller that is not there.
      4. A short method called once or twice goes back where it was called. Ninety of them. The name bought a reader nothing: it had to be looked up to learn what the one line did. Each was moved by hand with the file read back after it, and where a test named one, the case now holds what the method held.
      5. What may move is decided by what the body reaches. A body reaching anything private or protected stays where it is, since the caller may not reach it; so does one naming its own class, one whose file the caller does not import from, one that writes a file or reaches the database, a view drawing a screen, and an empty hook a subclass fills.
      6. The reading that decides what can be reached is made to match how Yioop calls things. A name built at run time, a name inside a string, a name spelt with different case, a name only a comment holds, and the root index.php all count now. Each was added after a method it had missed was taken away and something broke: the media updater, the server’s timer, the classifier tool.
      7. The dev scripts move into devlog/devscripts. check_git_clone.sh and drive_screens.js are gone with the browser scripts that had replaced the second, and every path inside the remaining scripts and in the process file points at the new folder.
      8. The wiki editor's page settings are read again. Splitting editWiki lifted the reading of them into editWikiHeadVars and left nothing calling it, so every setting a writer picked was dropped on save. editWikiPageSettings calls it now. Two cases pin what it reads, and check_orphans.sh is what found it.
    3. Find the files over five thousand lines. Eight are that long. Five are set aside: the three atto servers are another project's copy, PublicHelpPages.php is generated, and H264Decoder.php holds fifteen classes that belong together. The three that remain are Yioop's own, and they are read below by what each method is about and by how often one subject's methods call another's.
      1. The git pages move out of the wiki view. Twenty-three methods and 1,178 lines drawing a repository, its readme, its history, its statistics and its issues become GitElement, and WikiElement falls from 5,770 lines to 4,594. The wiki view reaches the new element by name through the view helper at the four places it drew a git page itself.
      2. The group model becomes a base with two children. GroupModel held groups, wiki pages and the feed in 6,793 lines. It now holds groups and their members in 2,623 lines. WikiModel holds the pages, their settings, their resources and the git issues. FeedModel holds the threads and posts. Both extend GroupModel, neither extends the other, and the base calls neither. The two page lookups both children need stayed in the base. Callers ask the model that holds what they want, which took 298 call sites across twenty files.
      3. Mail moves out of the social component. The 55 methods that read and write a mail account, its mailboxes and its messages become MailComponent, 3,723 lines, and SocialComponent falls from 16,932 lines to 13,358. Four calls cross the seam and each goes through the controller. The userMail activity moves to the new component in AdminController and GroupController, or the activity is not found at run time. Twenty-three tests on the request argument become three lists and one built method name.
      4. The wiki moves out of the social component. Its 53 wiki methods and the 21 git issue methods, about 6,250 lines with their support, become WikiComponent, and what stays is the feed, groups, contacts, ballots and bots at about 6,000. The activity wiki moves to the new component in AdminController, GroupController and ApiController. As built, five components came out of SocialComponent rather than one: the wiki, the group feed, the files kept with a page, the messages between two users and the git pages, and the base fell to 5,215 lines. The home of every method is settled first from what calls it, since moving whatever the last measurement named makes new crossings each pass. A method two components need sits in the base and is protected rather than private, and the constants a moved method reads move with it.
  5. Other work accomplished, oldest first. Each step below was reached while working on something above. It is kept here so the arc says what happened.
    1. A paste of resources lands in the language being looked at. A page has a copy in each language, each with resources of its own. The links out of the resources page dropped the language. A paste made on the Vietnamese page went to the English one. Every link from that page now carries it.
    2. A public page shows its group's header and footer. A group's owner sets a header page, a footer page and a theme once for the group. The group side of the site filled these into a page's head. The static side did not, so a p address showed neither. It fills them the same way now. A page names its own only where the group allows it.
    3. A locale named in an address reaches the page lookup. Yioop's own web server answers many requests in one process. The locale was held in a static that outlived a request. The first request settled the locale for every request after it, so l in a later address was ignored. RequestScope keeps a value in $_SERVER, which that server saves and puts back around every handover. A value there acts like a static but belongs to one request. The locale is kept there, and so is the list of names this machine answers to.
    4. Either part of a choosing list scrolls on its own. The chosen items and the rest now sit in a list of their own inside the outer one. A part holding more than seven items scrolls by itself. A long list of chosen items no longer pushes the rest off the screen. With nine items each, both parts stand 384 pixels tall over 642 pixels of items. With two and three, neither scrolls.
    5. A double click carries an item across the choosing line. A wiki form's choosek list has a line dividing what is chosen from the rest. Dragging across it is awkward on a long list. A double click below the line sends an item to the top of the chosen. One above it goes to the top of the rest.
    6. Read the sound a media file carries, in PHP alone. Yioop takes the sound out of a video and reads plain sound files. It decodes the kinds a browser sends. Each part below is measured against ffmpeg's decoding of the same file.
      1. Take the sound track out of a video file. SoundTrack finds the sound of an MP4, WebM or Ogg file without decoding a picture. Naming a video and a sound file writes that sound on its own. Five seconds of a WebM gave 240,960 samples as a WAV.
      2. Read and write the sound files that keep their samples as they are. PlainSound reads and writes WAV and AIFF. A file's kind is settled by its first bytes rather than its name. Sound passes through a couple of seconds at a time. A four minute song converts with no large file beside it.
      3. Decode AAC, the sound an MP4 usually carries. AacDecoder reads a frame part by part and all eleven tables of codes. Every one of ffmpeg's ninety-five frames for a tone decodes. A stereo song differs from ffmpeg by six thousandths a sample.
      4. Decode Vorbis, the sound an Ogg file usually carries. VorbisSetup reads the tables and VorbisDecoder turns packets into samples. Twenty seconds of a song differs from ffmpeg by one ten-thousandth a sample, with no shifting. The codes a book uses are worked out the way this format asks, which is not the usual way.
      5. Decode MP3. MP3 needs machinery of its own. That is a bank of filters, the side information beside each frame, and their tables. None of it is shared with the work already here.
      6. Write Vorbis and MP3, not only read them. An encoder for each would let a caller ask for either kind. Yioop needs neither today, so both wait.
    7. Mend how a media list draws its rows. Rows were drawn one over another and the count on a folder was painted over by it. The head of the list is closed whether or not a heading row is written. A cell is as tall as its row, and the count has a place of its own. Measured on a media list raised in the sandbox: rows at 98, 166, 234 and 302.
    8. Draw a document's cover the way the page itself draws it. A thumbnail is drawn by walking the page's own drawing. Four magazine covers went from 98, 15, 95 and 33 to 13.7, 13.2, 12.4 and 6.7 of 255. That measure is against another reader's rendering of the same page.
    9. A blob read asks only for what the file holds. The live server died asking for a string of 278 megabytes while near its limit. A read takes its length from an index and asks for all of it before reading. A wrong length asks for that much whatever the file holds. The length is cut to what is left in the file. One past 32 megabytes is refused, with a log line naming the file and offset.
    10. A stored video keeps its pictures. Every file put in a page's resource folder was offered to the voice message converter. It decided by the file's ending. A downloaded show named .mp4 was taken for a recording and written over with its sound alone. The converter now reads the file: one carrying pictures is left as it arrived.
    11. A podcast item is fetched with ffmpeg where it names the pieces of a video. downloadPodcastItem read such a playlist itself, chose a quality, fetched the pieces and joined them. ffmpeg does all of that from the playlist's address alone, so 173 lines came out for 124. A file that is not a playlist is fetched as a page with no ffmpeg run on it. The pieces are copied as they stand and nothing is written again. ffmpeg's progress reaches the log or the terminal every five seconds. Making a thumbnail for such a video asked a smoothing table for a column it does not hold, which stopped the media updater.
    12. A picture file gets its own thumbnail drawn. A thumbnail is kept under the file's whole name with .webp added. The one for a_picture.webp is a_picture.webp.webp. The controller left the ending off where a name already carried it. That asked for the file itself and drew nothing. It adds the ending wherever such a file stands beside it, and leaves the bare name only where none does.
    13. Choosing a language keeps the screen a writer is on. The language dropdown built an address ending arg=edit and nothing else. A writer looking at a page's resources landed on its text opened for editing. It keeps what is being looked at now, read from the same two names the rest of the view reads. A redirect keeps the language too, so a paste comes back on the copy it was made on.
    14. The emoji picker drops the descriptions that overrode a screen reader. Each emoji carried a short name a writer types and a description, and the description became a label. A reader's own software names an emoji in the reader's own language, so the label replaced that with one language for everybody. The 1,823 descriptions are gone from 25 locales. The array moved out of the constructor into a method of its own.
    15. Two long view methods are split by fieldset. ServersElement::render drew six fieldsets in one method of 1,096 lines, and ManagegroupsElement::renderGroupsForm drew five in 601. Each fieldset is now a method named for the help button already in it. A fieldset works out for itself what it needs about the form, since a value the old method held is not there once the fieldset moves out.
    16. The wiki editor is split into four methods. renderEditPageForm was 1,093 lines. It is now 493, with renderEditSettings, renderEditBody and renderEditButtons beside it. The thirteen facts its markup reads throughout became properties first, since a stretch cannot move while those are locals. Five names ending in a kind were renamed with them. The whole page, compared byte for byte on all three screens, differs only where the token differs.
    17. The group feed's switch becomes eighteen methods. groupFeeds ran to 1,070 lines because every thing a reader can do to a feed sat in one switch. Each arm is now a method named for what it does, and the switch chooses between them. Two labels sharing a body share a method, and a break inside a moved arm became a return.
    18. A VP8 macroblock predicted in sub-blocks no longer reads past its residuals. reconstructB overwrote its two loop counters with sample positions inside the loop that used them. After the first sub-block the residual was looked for at a key past the sixteen a macroblock holds, and every frame wrote warnings. The sample positions are named apart from the counters.
    19. A VP8 macroblock predicted in sub-blocks no longer reads past its residuals. reconstructB overwrote its two loop counters with sample positions inside the loop that used them. After the first sub-block the residual was looked for at a key past the sixteen a macroblock holds, and every such frame wrote warnings. The sample positions are named apart from the counters.
    20. A search asks for no more results than a reader would. The limit a request sends decides how many results are gathered. It also decides how many summaries are loaded after them. It was unbounded, so one request could ask for as much memory as it liked. It is held at a thousand now. A posting list claiming more items than its bytes can hold is refused.
    21. The eighteen feed methods are given the values their bodies read. Splitting groupFeeds left each moved body naming locals the old method had worked out above the switch. Sixteen of the eighteen named a group model and none set it, so emptying the clipboard died on a call to a method on null. Each method works out what it reads now, and two small methods give the request's numbers and a post's title and description.
    22. A cut post is taken away again after being copied. Before the split the cutpost label fell through into deletepost, and the split turned that into a break, so a cut copied and left the post standing. The switch falls through again. Pasting every clipped post also copied resources from an item that was never named, where it meant the item the loop is on.
    23. The wiki activity is split into eleven methods. wiki ran to 930 lines, holding a switch of twelve labels and 318 lines of drawing after it. It is 189 lines now. The four long arms, the field cleaning, and the three parts of the drawing each became a method. The loop that made locals out of field names is gone, so a reader and a checker can both see what each part reads. What the method hands the view on twelve screens is unchanged, byte for byte.
    24. A checkmark no longer stops a patch being cut. The cut refused a plan whose lines were rewritten, which is what setting a checkmark does. A removed line whose added counterpart differs only in its marker spans now counts as a marker change rather than a removal. A line that leaves the plan outright still stops the cut.
    25. The wiki editor's activity is split into six methods. editWiki ran to 906 lines. It is 198 now, with the file kept beside a page, the page's own settings, the values at the head of a page, the actions on the files beside it, and taking a file up, each a method. What the activity hands the view on seventeen screens is unchanged, byte for byte, the five new ones covering the settings screen, a page that is not there yet, a file opened for writing, a writing mark put back, and the files listing open.
    26. The parts of the wiki and feed activities that stand on their own are checked by unit tests. Six cases read the request fields the two activities work from: a number that is absent, a zero, a title and a description cut to the length a post may hold, a page name written with a space, the two fields the wiki cannot do without, a group named by its name, and a page filled in from a template. Three methods were made public so a case can reach them. The whole suite runs 1,096 cases and 43,671 assertions.
    27. Naming a group by its name clears the report that the group is absent. A request naming a group by name and not by number was refused for missing fields, since the report was cleared under the name of the field being read rather than under the name of the group. A unit test found this. It is cleared under group_id now, which is the name the report is written under.
    28. Fifteen screens are driven in a browser and match what they drew before. Yioop was stood up and a headless Chrome signed in and walked the wiki and the feed: reading, the page list plain and filtered, the source, four editing screens, the history, the folder, the related pages, and the feed. Each was walked against the confirmed tree and against this one, and what the browser read off the pages is the same.
    29. CodeTool gains a probe that says what an activity hands its view. A long method is split by moving parts of it out, and the question that answers is whether any screen it serves now shows something different. probe calls one activity once per screen named in a file and prints the whole of what it hands the view. The reader's mark, a hash and a count of seconds are written over, so two runs of an unchanged activity match to the byte.
    30. The audit runs as one command. The eleven checks before a patch were run one at a time over eight scripts and the table was written by hand, so a table could say a check passed that was never run. devlog/audit.sh runs them over the files the patch carries and prints the table itself. It found a sandbox Config.php committed into this very patch.
    31. A sandbox says what it is missing. devlog/check_env.sh names what a work clone needs and what is absent: PHP and its extensions, the word list the docblock check reads, a configured work directory, the two databases, and a browser to drive. The word list being absent had turned one real docblock fault into ninety-one, which is the kind of absence that hides rather than stops.
    32. An old file is brought up one patch at a time. A patch touching a file with two hundred docblocks that say nothing cannot rewrite them all and do its own work. The cut now refuses only the first such file the patch touches and names the rest as waiting. The test file for the group and wiki component is this patch's, along with the tool file it touches.
    33. A browser walks the same screens the probe reads. devlog/drive_screens.js signs in to a standing Yioop and fetches each screen a file names, reading what the browser puts on the page. Each screen is fetched in a context of its own, so a message one leaves cannot show on the next, which is the fault that made an earlier comparison disagree with itself. How long ago a row changed is written over, so two runs a minute apart match to the byte.
    34. A log message that repeats is counted rather than written every time. Reading a broken index wrote tens of thousands of entries a second until the log was the slow part of the machine. crawlLogCounter stands in for crawlLog and writes one summary every so many seconds. It holds the count in memory and touches no file. The four posting decode guards call it, and their lines of figures are gone, since each figure would be counted as a message of its own.